🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / core / ui / src / commonMain / kotlin / org / meshtastic / core / ui / viewmodel / ViewModelExtensions.kt
Displaying Raw • Download
core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/viewmodel/ViewModelExtensions.kt e330442dd5415dc7eb2bff4e3a3f1599c18022a1 (e330442d) Text, 6.12 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tf0883e@fileTb4b4b4:Te6edf3SuppressTb4b4b4(Ta5d6ff"Ta5d6ffWrappingTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffUnusedImportsTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffSpacingAroundColonTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTooGenericExceptionCaughtTa5d6ff"Tb4b4b4)
Tff7b72package T7ee787org.meshtastic.core.ui.viewmodel
Tff7b72import T7ee787androidx.lifecycle.ViewModel
Tff7b72import T7ee787androidx.lifecycle.viewModelScope
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.coroutines.CancellationException
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.Job
Tff7b72import T7ee787kotlinx.coroutines.flow.Flow
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableSharedFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.SharingStarted
Tff7b72import T7ee787kotlinx.coroutines.flow.StateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.catch
Tff7b72import T7ee787kotlinx.coroutines.flow.map
Tff7b72import T7ee787kotlinx.coroutines.flow.onStart
Tff7b72import T7ee787kotlinx.coroutines.flow.stateIn
Tff7b72import T7ee787kotlinx.coroutines.launch
Tff7b72import T7ee787org.meshtastic.core.common.log.expectedConditionLabel
Tff7b72import T7ee787org.meshtastic.core.resources.Res
Tff7b72import T7ee787org.meshtastic.core.resources.UiText
Tff7b72import T7ee787org.meshtastic.core.resources.unknown_error
Tff7b72import T7ee787kotlin.coroutines.CoroutineContext
Tff7b72import T7ee787kotlin.coroutines.EmptyCoroutineContext
Tff7b72import T7ee787kotlin.time.Duration
Tff7b72import T7ee787kotlin.time.Duration.Companion.seconds
T8b949e/**
* Extension for converting a [Flow] to a [StateFlow] in a [ViewModel] context.
*
* @param initialValue the initial value of the state flow
* @param stopTimeout configures a delay between the disappearance of the last subscriber and the stopping of the
* sharing coroutine.
*/
Te6edf3contextTb4b4b4(Te6edf3viewModelTb4b4b4: Te6edf3ViewModelTb4b4b4)
Tff7b72fun Tff7b72<Te6edf3TTff7b72> Td2a8ffFlowTff7b72<Te6edf3TTff7b72>Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3initialValueTb4b4b4: Te6edf3TTb4b4b4, Te6edf3stopTimeoutTb4b4b4: Te6edf3Duration Tff7b72= T79c0ff5.Te6edf3secondsTb4b4b4)Tb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3TTff7b72> Tff7b72= Te6edf3stateInTb4b4b4(
Te6edf3scope Tff7b72= Te6edf3viewModelTb4b4b4.Te6edf3viewModelScopeTb4b4b4,
Te6edf3started Tff7b72= Te6edf3SharingStartedTb4b4b4.Te6edf3WhileSubscribedTb4b4b4(Te6edf3stopTimeoutMillis Tff7b72= Te6edf3stopTimeoutTb4b4b4.Te6edf3inWholeMillisecondsTb4b4b4)Tb4b4b4,
Te6edf3initialValue Tff7b72= Te6edf3initialValueTb4b4b4,
Tb4b4b4)
T8b949e// ---------------------------------------------------------------------------
T8b949e// UiState: shared Loading / Content / Error wrapper
T8b949e// ---------------------------------------------------------------------------
T8b949e/**
* Lightweight tri-state wrapper for UI data. Prefer this over bare nullable initial values when the UI needs to
* distinguish "still loading" from "genuinely empty."
*/
Tff7b72sealed Tff7b72interface T56d364UiStateTff7b72<Tff7b72out Te6edf3TTff7b72> Tb4b4b4{
T8b949e/** Data has not yet arrived. */
Tff7b72data Tff7b72object T56d364Loading Tb4b4b4: Te6edf3UiStateTff7b72<Te6edf3NothingTff7b72>
T8b949e/** Data is available. */
Tff7b72data Tff7b72class T56d364ContentTff7b72<Te6edf3TTff7b72>Tb4b4b4(Tff7b72val Te6edf3dataTb4b4b4: Te6edf3TTb4b4b4) Tb4b4b4: Te6edf3UiStateTff7b72<Te6edf3TTff7b72>
T8b949e/** An error occurred while loading. */
Tff7b72data Tff7b72class T56d364ErrorTb4b4b4(Tff7b72val Te6edf3messageTb4b4b4: Te6edf3UiTextTb4b4b4) Tb4b4b4: Te6edf3UiStateTff7b72<Te6edf3NothingTff7b72>
Tb4b4b4}
T8b949e/** Returns the [Content] data, or `null` if this state is [Loading] or [Error]. */
Tff7b72fun Tff7b72<Te6edf3TTff7b72> Td2a8ffUiStateTff7b72<Te6edf3TTff7b72>Tb4b4b4.Te6edf3dataOrNullTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3T? Tff7b72= Tb4b4b4(Tff7b72this Tff7b72as? Te6edf3UiStateTb4b4b4.Te6edf3ContentTb4b4b4)Tff7b72?.Te6edf3data
T8b949e/**
* Wraps this [Flow] into a `StateFlow<UiState<T>>`, emitting [UiState.Loading] until the first value, then
* [UiState.Content] for each emission. Upstream errors are caught and mapped to [UiState.Error].
*/
Te6edf3contextTb4b4b4(Te6edf3viewModelTb4b4b4: Te6edf3ViewModelTb4b4b4)
Tff7b72fun Tff7b72<Te6edf3TTff7b72> Td2a8ffFlowTff7b72<Te6edf3TTff7b72>Tb4b4b4.Te6edf3asUiStateTb4b4b4(Te6edf3stopTimeoutTb4b4b4: Te6edf3Duration Tff7b72= T79c0ff5.Te6edf3secondsTb4b4b4)Tb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3UiStateTff7b72<Te6edf3TTff7b72>Tff7b72> Tff7b72=
Tff7b72thisTb4b4b4.Te6edf3mapTff7b72<Te6edf3TTb4b4b4, Te6edf3UiStateTff7b72<Te6edf3TTff7b72>Tff7b72> Tb4b4b4{ Te6edf3UiStateTb4b4b4.Te6edf3ContentTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3onStart Tb4b4b4{ Te6edf3emitTb4b4b4(Te6edf3UiStateTb4b4b4.Te6edf3LoadingTb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3catch Tb4b4b4{ Te6edf3e Tff7b72-Tff7b72>
Tff7b72val Te6edf3message Tff7b72= Te6edf3eTb4b4b4.Te6edf3messageTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3UiTextTb4b4b4.Te6edf3DynamicStringTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4} Tff7b72?: Te6edf3UiTextTb4b4b4.Te6edf3ResourceTb4b4b4(Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3unknown_errorTb4b4b4)
Te6edf3emitTb4b4b4(Te6edf3UiStateTb4b4b4.Te6edf3ErrorTb4b4b4(Te6edf3messageTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3initialValue Tff7b72= Te6edf3UiStateTb4b4b4.Te6edf3LoadingTb4b4b4, Te6edf3stopTimeout Tff7b72= Te6edf3stopTimeoutTb4b4b4)
T8b949e// ---------------------------------------------------------------------------
T8b949e// safeLaunch: CancellationException-safe coroutine launcher with error routing
T8b949e// ---------------------------------------------------------------------------
T8b949e/**
* Launches a coroutine in [viewModelScope] that catches all exceptions except [CancellationException]. Non-cancellation
* errors are logged and emitted to [errorEvents] (if provided) for one-shot UI consumption (e.g. snackbar / toast).
*
* @param context optional [CoroutineContext] element (typically a dispatcher) merged into the launch. Defaults to
* [EmptyCoroutineContext], inheriting [viewModelScope]'s dispatcher.
*
* ```
* // In a ViewModel:
* safeLaunch(errorEvents = _errors) {
* repository.saveData(data)
* }
* ```
*/
Te6edf3contextTb4b4b4(Te6edf3viewModelTb4b4b4: Te6edf3ViewModelTb4b4b4)
Tff7b72fun Td2a8ffsafeLaunchTb4b4b4(
Te6edf3contextTb4b4b4: Te6edf3CoroutineContext Tff7b72= Te6edf3EmptyCoroutineContextTb4b4b4,
Te6edf3errorEventsTb4b4b4: Te6edf3MutableSharedFlowTff7b72<Te6edf3UiTextTff7b72>Tff7b72? Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3tagTb4b4b4: Tffa657String? Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3blockTb4b4b4: Te6edf3suspend Te6edf3CoroutineScopeTb4b4b4.Tb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3Job Tff7b72= Te6edf3viewModelTb4b4b4.Te6edf3viewModelScopeTb4b4b4.Te6edf3launchTb4b4b4(Te6edf3contextTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Te6edf3blockTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
Tff7b72throw Te6edf3e
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3label Tff7b72= Te6edf3tag Tff7b72?: Ta5d6ff"Ta5d6ffsafeLaunchTa5d6ff"
T8b949e// Expected conditions (Bluetooth off, permission not granted, …) still reach the user as an error
T8b949e// event, but are logged at warn so neither Crashlytics nor Datadog RUM records them as a defect.
Tff7b72val Te6edf3expectedLabel Tff7b72= Te6edf3eTb4b4b4.Te6edf3expectedConditionLabelTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3expectedLabel Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ff[Tffd700$Te6edf3labelTa5d6ff] Expected condition: Tffd700$Te6edf3expectedLabelTa5d6ff" Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ff[Tffd700$Te6edf3labelTa5d6ff] Unhandled exceptionTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tff7b72val Te6edf3message Tff7b72= Te6edf3eTb4b4b4.Te6edf3messageTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3UiTextTb4b4b4.Te6edf3DynamicStringTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4} Tff7b72?: Te6edf3UiTextTb4b4b4.Te6edf3ResourceTb4b4b4(Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3unknown_errorTb4b4b4)
Te6edf3errorEventsTff7b72?.Te6edf3tryEmitTb4b4b4(Te6edf3messageTb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Creates and returns a [MutableSharedFlow] intended for one-shot error events. Expose as `Flow` via
* [kotlinx.coroutines.flow.asFlow] in the ViewModel (hiding hot-flow semantics), and collect in the UI to show
* snackbars or toasts.
*/
Tff7b72fun Td2a8fferrorEventFlowTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3MutableSharedFlowTff7b72<Te6edf3UiTextTff7b72> Tff7b72= Te6edf3MutableSharedFlowTb4b4b4(Te6edf3extraBufferCapacity Tff7b72= T79c0ff1Tb4b4b4)
Served by rngit 1.5.0 - Generated in 0.05s